package com.raccoonagents.tools.cascet2d.model;
import java.util.ArrayList;

import com.raccoonagents.tools.cascet2d.interfaces.IStateController;
import com.raccoonagents.tools.cascet2d.util.FWLogger;

/**
 * advanced state implementation
 * @author jabi
 *
 */
public class AppState {
	public static final int PERFORM_ONCE = 0;	
	public static final int PERFORM_FOREVER = -1;
	public static final int START_AT_END = -2;

	private final int STATE_FREEZED = 1;
	private final int STATE_ACTIVATED = 2;
	
	private boolean taskDone = false;
	public final int id;
	public long duration = 0;
	public float rate = 0;

	public long elapsed = 0;
	protected boolean neverPerformed = true;

	private final IStateController controller;
	private final ArrayList<AppState> children = new ArrayList<AppState>();
	private long start;
	private int currentState = STATE_FREEZED;
	private Object metaData = null;
		

	/** info about is parent node allowed to activate this node's sequence */
	private final boolean allowParentActivate;

	public AppState(final int id, final IStateController controller, final long start, final long duration, final boolean allowParentActivate){
		this.duration = duration;
		this.start = start;
		this.id = id;
		this.controller = controller;
		this.allowParentActivate = allowParentActivate;
	}
	

	
	/**
	 *
	 */
	public long getStart() {
		return start;
	}

	
	public void addChild(final AppState child){

		if(child.getStart() == START_AT_END){
			child.start = duration;
		}
		
		children.add(child);		
	}
	
	public boolean isActive(){
		return currentState == STATE_ACTIVATED;
	}
	
	/** task can be done or activated (still updates) */
	public boolean isRunning() {
		return currentState == STATE_ACTIVATED || taskDone;
	}

	public void activate() {
		
		FWLogger.info("id = " + id);
		currentState = STATE_ACTIVATED;
		controller.onStart(this);
	}


	public ArrayList<AppState> getChildren() {
		return children;
	}
	
	public void resume(){
		FWLogger.info("id = " + id);
		currentState = STATE_ACTIVATED;		
	}
	
	public boolean isTaskDone(){
		return taskDone;
	}
	
	public void pause(){
		FWLogger.info("id = " + id);
		currentState = STATE_FREEZED;
	}
	
	/** Resets the whole hierarchy of this state */
	public void reset(){
		FWLogger.info("id = " + id);
		elapsed = 0;
		currentState = STATE_FREEZED;
		neverPerformed = true;
		rate = 0;		
		taskDone = false;
		for(final AppState child : children){
			child.reset();
		}
	}


	/**
	 * Notifies controller this state was finished. After this notification, this state will
	 * not nofity controller about it's update.
	 */
	public void markTaskDone(){
		FWLogger.info("id = " + id);
		controller.onEnd(this);
		
		taskDone = true;
	}
	/***/
	public void update(final long dt){
		
		if(currentState == STATE_FREEZED){
			return;
		}
		
		controller.notifyWasAccessed(this);
		
		
		// Elapsed needs to be updated as child could activate long after this has finished?
		elapsed += dt;
		
		if(!taskDone){
			// makes state to perform once if time was set to negative or 0
			if((duration == PERFORM_ONCE && neverPerformed) || duration == PERFORM_FOREVER){
				rate = 1.0f;
			}else{			
				rate = (float)elapsed / (float)duration;
			}
			
			if(rate >= 0.0f && rate <= 1.0f){
				neverPerformed = false;
				controller.update(this, dt);
			}

			// Quarantees atleast one perform operation for each state.
			if(rate >= 1.0f && neverPerformed){
				controller.update(this, dt);
			}			
		}

		for(final AppState child : children){
			if(elapsed >= child.getStart()){
				
				if(!child.isActive() && !child.taskDone && child.allowParentActivate) {
					FWLogger.info("Task " + id + " is activating " + child.id);
					child.activate();
				}
				
				if(child.isRunning()){					
					child.update(dt);
				}
			}
		}					
		
		
		if(rate >= 1.0f && duration != PERFORM_FOREVER && !taskDone){
			markTaskDone();
		}
	}

	
	public boolean isAllowParentActivate() {
		return allowParentActivate;
	}

	public Object getMetaData() {
		return metaData;
	}

	public void setMetaData(final Object metaData) {
		this.metaData = metaData;
	}
}
